<?php
/**
* User, Role and permissions management.
*
* Copyright (c) 2009 James Gauld <james@jamesgauld.net>
* This file is part of Scribble.
* @license http://www.scribblecms.co.uk/license.txt
*
* @package Scribble
*/
use Buan\Config;
use Buan\Model;
use Buan\ModelCriteria;
use Buan\ModelManager;
use Buan\SystemLog;
class AuthController extends ScribbleAdminController {

	/**
	* Displays an overview of recent user activity
	*
	* @param array Action parameters (none supported)
	* @return ScribbleAdminView
	*/
	public function index($params) {

		// Init
		$view = new ScribbleAdminView();
		if(!$this->init($view)) {
			return $view;
		}
		$view->setSource(Config::get('ext.Scribble.dir.views').'/scribble/auth/index.tpl.php');

		// Prepare secondary view
		$sView = new ScribbleAdminView();
		$sView->setSource(Config::get('ext.Scribble.dir.views').'/scribble/auth/common.secondary.tpl.php');
		ScribbleAdminView::getGlobalView()->attachViewToSlot($sView, 'secondary');

		// Result
		return $view;
	}

	/**
	* Login to the session as the specified user.
	*
	* @param string User ID
	* @return ScribbleAdminView
	*/
	private function loginAs($userId) {

		// Init
		$view = new ScribbleAdminView();
		if(!$this->init($view)) {
			return $view;
		}
		$view->setSource(Config::get('ext.Scribble.dir.views').'/scribble/auth/login-as.tpl.php');

		// Prepare secondary view
		$sView = new ScribbleAdminView();
		$sView->setSource(Config::get('ext.Scribble.dir.views').'/scribble/auth/common.secondary.tpl.php');
		ScribbleAdminView::getGlobalView()->attachViewToSlot($sView, 'secondary');

		// Load user
		$user = Model::create('User');
		$user->id = $userId;
		if(!$user->getModelManager()->load($user)) {
			SystemLog::add('Could not find the specified user.', SystemLog::WARNING);
			return new ScribbleAdminView();
		}

		// If user is already logged into the session, or the active user does not
		// have the privilege to login
		if(!ScribbleSession::isUserQueued($user->id) && !ScribbleAuth::authorize(ScribbleSession::getUser(), 'user-multi-login')) {
			SystemLog::add('Access denied. You do not have permission to login as other users.', SystemLog::WARNING);
			return new ScribbleAdminView();
		}

		// Login as the user
		else if(!ScribbleSession::login($user)) {
			SystemLog::add('Failed to login as the user.', SystemLog::WARNING);
			return new ScribbleAdminView();
		}

		// Result
		$view->user = $user;
		return $view;
	}

	/**
	* List Roles.
	* Support AJAX requests and delivers in json format.
	*
	* @param array Parameters (0='create' | role id)
	* @return ScribbleAdminView
	*/
	public function role($params) {

		// Init
		$view = new ScribbleAdminView();
		if(!$this->init($view)) {
			return $view;
		}
		$view->setSource(Config::get('ext.Scribble.dir.views').'/scribble/auth/role.list.tpl.php');

		// Prepare secondary view
		$sView = new ScribbleAdminView();
		$sView->setSource(Config::get('ext.Scribble.dir.views').'/scribble/auth/common.secondary.tpl.php');
		ScribbleAdminView::getGlobalView()->attachViewToSlot($sView, 'secondary');

		// Create/edit role
		if(isset($params[0]) && $params[0]==='create') {
			return $this->roleEdit('');
		}
		else if(isset($params[0]) && $params[0]!='') {
			return $this->roleEdit($params[0]);
		}

		// Count the no. users in each role
		$roleUserCount = array();
		$tp = Config::get('ext.Scribble.dbTablePrefix');
		$sql = "SELECT COUNT(*) AS numusers, role_id FROM {$tp}user_role GROUP BY role_id";
		$stmt = ModelManager::sqlQuery($sql);
		while($rec = $stmt->fetch(PDO::FETCH_OBJ)) {
			$roleUserCount[$rec->role_id] = $rec->numusers;
		}

		// Load all roles based on any applied filters
		$c = new ModelCriteria();
		$roles = ModelManager::select('Role', $c);
		foreach($roles as $r) {
			$r->_numusers = isset($roleUserCount[$r->id]) ? $roleUserCount[$r->id] : 0;
		}
		$view->roles = $roles;

		// Result (for ajax)
		if(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']=='XMLHttpRequest') {
			foreach($roles as $k=>$r) {
				$roles[$k] = $r->getDbData();
			}
			echo json_encode($roles->asArray());
			exit(0);
		}

		// Result (default)
		else {
			return $view;
		}
	}

	/**
	* Create or edit a role.
	*
	* @param string Role ID (blank if creating a new role)
	* @return ScribbleAdminView
	*/
	private function roleEdit($roleId) {

		// Init
		$view = new ScribbleAdminView();
		if(!$this->init($view)) {
			return $view;
		}
		$view->setSource(Config::get('ext.Scribble.dir.views').'/scribble/auth/role.edit.tpl.php');

		// Prepare secondary view
		$sView = new ScribbleAdminView();
		$sView->setSource(Config::get('ext.Scribble.dir.views').'/scribble/auth/common.secondary.tpl.php');
		ScribbleAdminView::getGlobalView()->attachViewToSlot($sView, 'secondary');

		// Auth: Can the active user create or modify roles?
		if(!ScribbleAuth::authorize(ScribbleSession::getUser(), 'user-admin')) {
			SystemLog::add("You don't have permission to create or modify roles.", SystemLog::WARNING);
			return new ScribbleAdminView();
		}

		// Load role
		$role = Model::create('Role');
		if($roleId!='') {
			$role->id = $roleId;
			if(!$role->getModelManager()->load($role)) {
				SystemLog::add("Sorry, that role could not be found. Check that you've entered the ID correctly and try again.", SystemLog::WARNING);
				return new ScribbleAdminView();
			}
		}
		$view->role = $role;

		// Delete
		if(!empty($_POST) && isset($_POST['method']) && $_POST['method']=='delete') {

			// The role must not be in use by any Users
			$dRoleId = $role->id;
			$dRoleTitle = $role->title;
			if(!$role->findRelatives('User')->isEmpty()) {
				SystemLog::add('You cannot remove this Role as it is still being used by some users. Move these users to another role before trying again.', SystemLog::WARNING);
			}

			// Delete: fail
			else if(!$role->getModelManager()->delete($role)) {
				SystemLog::add('Failed to delete the role.', SystemLog::WARNING);
			}

			// Delete: success
			else {
				$aUser = ScribbleSession::getUser();
				LogManager::add("Role role:{$dRoleId}:{$dRoleTitle} was deleted by user:{$aUser->id}:{$aUser->displayname}", LogManager::USER_ACTION, $aUser->id);
				SystemLog::add('Role has been deleted successfully.', SystemLog::INFO);
			}
		}

		// Save
		else if(!empty($_POST)) {

			// Gather
			$isInDatabase = $role->isInDatabase();
			$role->title = $_POST['title'];
			$role->description = $_POST['description'];

			// Gather privileges
			$privsToGrant = $_POST['pallow_all']==1 ? '*' : (
				isset($_POST['privilege']) ? implode(",", $_POST['privilege']) : ''
			);

			// Validate
			$validated = TRUE;
			if(preg_replace("/[^a-z]/i", "", $role->title)=='') {
				SystemLog::add('Please provide a valid, non-empty title for this role.', SystemLog::WARNING);
				$validated = FALSE;
			}
			if(preg_replace("/[^a-z]/i", "", $role->title)=='') {
				SystemLog::add('Please provide a valid, non-empty description for this role.', SystemLog::WARNING);
				$validated = FALSE;
			}
			if(!$validated) {
			}

			// Save: fail
			else if(!$role->getModelManager()->save($role)) {
				SystemLog::add('Failed to save this role.', SystemLog::WARNING);
			}

			// Save: success
			else {
				// Revoke and re-grant privileges
				if(!ScribbleAuth::revoke($role, '*')) {
					SystemLog::add('Failed to revoke premissions for resetting.', SystemLog::WARNING);
				}
				else if(!ScribbleAuth::grant($role, $privsToGrant)) {
					SystemLog::add('Failed to grant premissions.', SystemLog::WARNING);
				}
				SystemLog::add('Role was saved successfully.', SystemLog::INFO);$aUser = ScribbleSession::getUser();

				$aUser = ScribbleSession::getUser();
				LogManager::add("Role role:{$role->id}:{$role->title} was ".($isInDatabase ? 'modified' : 'created')." by user:{$aUser->id}:{$aUser->displayname}", LogManager::USER_ACTION, $aUser->id);
			}
		}

		// Gather all defined privileges
		$allPrivs = ScribbleAuth::getDefinedPrivileges();
		$view->privileges = ScribbleAuth::getDefinedPrivilegesByCategory();
		$view->rolePrivileges = ScribbleAuth::getRolePrivileges($role);
		$view->pallowAll = count((array)$view->rolePrivileges)==count($allPrivs) ? 1 : 0;

		// Result
		return $view;
	}

	/**
	* List users. Also captures edit/create requests and forwards to userEdit().
	*
	* @param array Parameters (0='create' | user id)
	* @return ScribbleAdminView
	*/
	public function user($params) {

		// Init
		$view = new ScribbleAdminView();
		if(!$this->init($view)) {
			return $view;
		}
		$view->setSource(Config::get('ext.Scribble.dir.views').'/scribble/auth/user.list.tpl.php');

		// Prepare secondary view
		$sView = new ScribbleAdminView();
		$sView->setSource(Config::get('ext.Scribble.dir.views').'/scribble/auth/user.list.secondary.tpl.php');
		ScribbleAdminView::getGlobalView()->attachViewToSlot($sView, 'secondary');

		// Create/edit user or login as the user
		if(isset($params[0]) && $params[0]==='create') {
			return $this->userEdit('');
		}
		else if(isset($params[0]) && $params[0]!='') {
			if(isset($_GET['loginas'])) {
				return $this->loginAs($params[0]);
			}
			return $this->userEdit($params[0]);
		}

		// Gather filters
		$filter = array(
			'role'=>''
		);
		if(isset($_GET['filter'])) {
			foreach($_GET['filter'] as $k=>$v) {
				$filter[$k] = $v;
			}
		}
		$sView->filter = $filter;

		// Load all roles for use in the filtering form
		$rolesForFilter = ModelManager::select('Role')->asArray();
		foreach($rolesForFilter as $k=>$v) {
			$rolesForFilter[$k] = "{$v->title}|{$v->id}";
		}
		$sView->rolesForFilter = $rolesForFilter;

		// Load all users based on any applied filters
		$tp = Config::get('ext.Scribble.dbTablePrefix');
		$c = new ModelCriteria();

		// Filter: Role
		if(!empty($filter['role'])) {
			$c->leftJoin("{$tp}user_role", "{$tp}user.id={$tp}user_role.user_id");
			$c->leftJoin("{$tp}role", "{$tp}user_role.role_id={$tp}role.id");
			$c->addClause(ModelCriteria::LIKE, "{$tp}role.title", "%{$filter['role']}%");
		}

		// Execute query
		$users = ModelManager::select('User', $c);
		$view->users = $users;

		// Result
		return $view;
	}

	/**
	* Create or edit a user.
	*
	* @param string User ID (blank if creating a new user)
	* @return ScribbleAdminView
	*/
	private function userEdit($userId) {

		// Init
		$view = new ScribbleAdminView();
		if(!$this->init($view)) {
			return $view;
		}
		$view->setSource(Config::get('ext.Scribble.dir.views').'/scribble/auth/user.edit.tpl.php');

		// Prepare secondary view
		$sView = new ScribbleAdminView();
		$sView->setSource(Config::get('ext.Scribble.dir.views').'/scribble/auth/common.secondary.tpl.php');
		ScribbleAdminView::getGlobalView()->attachViewToSlot($sView, 'secondary');

		// Auth: Can the active user create or modify user accounts?
		if(!ScribbleAuth::authorize(ScribbleSession::getUser(), 'user-admin')) {
			SystemLog::add("You don't have permission to create or modify user accounts.", SystemLog::WARNING);
			return new ScribbleAdminView();
		}

		// Load user
		$user = Model::create('User');
		if($userId!='') {
			$user->id = $userId;
			if(!$user->getModelManager()->load($user)) {
				SystemLog::add("Sorry, that user could not be found. Check that you've entered the ID correctly and try again.", SystemLog::WARNING);
				return new ScribbleAdminView();
			}
		}
		$view->user = $user;

		// Delete
		if(!empty($_POST) && isset($_POST['method']) && $_POST['method']=='delete') {
			$dUserId = $user->id;
			$dUserName = $user->displayname;
			$aUser = ScribbleSession::getUser();

			// Cannot remove your own account
			if($aUser->id===$user->id) {
				SystemLog::add('You cannot remove your own account!', SystemLog::WARNING);
			}

			// Delete: fail
			else if(!$user->getModelmanager()->delete($user)) {
				SystemLog::add('Failed to delete the user.', SystemLog::WARNING);
			}

			// Delete: Success
			else {
				SystemLog::add('User has been removed successfully.', SystemLog::INFO);
				$user = Model::create('User');
				$view->user = $user;

				LogManager::add("Page user:{$dUserId}:{$dUserName} was deleted by user:{$aUser->id}:{$aUser->displayname}", LogManager::USER_ACTION, $aUser->id);
			}
		}

		// Save
		else if(!empty($_POST)) {

			// Gather
			$isInDatabase = $user->isInDatabase();
			$user->email = $_POST['email'];
			$user->displayname = $_POST['displayname'];
			if($_POST['password']!='') {
				if(!$user->isInDatabase()) {
					$user->hashsalt = UserModel::generateHashSalt();
				}
				$user->password = $user->encryptPassword($_POST['password']);
			}

			// Gather roles
			$rolesToJoin = isset($_POST['role']) ? $_POST['role'] : array();

			// Validate
			$validated = TRUE;
			if(empty($rolesToJoin)) {
				SystemLog::add('A user must belong to at least one role.', SystemLog::WARNING);
				$validated = FALSE;
			}
			if($user->password=='') {
				SystemLog::add('You must define a password for this user.', SystemLog::WARNING);
			}
			if(!$validated) {
			}

			// Save: Fail
			else if(!$user->getModelManager()->save($user)) {

			}

			// Save: success
			else {
				// Remove all roles associations
				$tp = Config::get('ext.Scribble.dbTablePrefix');
				$sql = "DELETE FROM {$tp}user_role WHERE user_id=?";
				ModelManager::sqlQuery($sql, array($user->id));

				// Add new associations
				foreach($rolesToJoin as $rId) {
					$ur = Model::create('UserRole');
					$ur->role_id = $rId;
					$ur->addRelatives($user);
					$ur->getModelmanager()->save($ur);
				}

				SystemLog::add('User was saved successfully.', SystemLog::INFO);

				$aUser = ScribbleSession::getUser();
				LogManager::add("User user:{$user->id}:{$user->displayname} was ".($isInDatabase ? 'modified' : 'created')." by user:{$aUser->id}:{$aUser->displayname}", LogManager::USER_ACTION, $aUser->id);
			}
		}

		// Load roles
		$userRoles = array();
		$userRoleModels = $user->findRelatives('Role');
		foreach($userRoleModels as $urm) {
			$userRoles[$urm->id] = $urm;
		}
		$c = new ModelCriteria();
		$c->addOrder('title');
		$view->roles = ModelManager::select('Role', $c);
		$view->userRoles = $userRoles;

		// Result
		return $view;
	}
}
?>